{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<font color=\"red\">注</font>: 使用 tensorboard 可视化需要安装 tensorflow (TensorBoard依赖于tensorflow库，可以任意安装tensorflow的gpu/cpu版本)\n",
    "\n",
    "```shell\n",
    "pip install tensorflow-cpu\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:41:29.893914Z",
     "start_time": "2025-01-20T07:41:27.487296Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:46:03.496622Z",
     "iopub.status.busy": "2025-02-02T13:46:03.496140Z",
     "iopub.status.idle": "2025-02-02T13:46:10.313132Z",
     "shell.execute_reply": "2025-02-02T13:46:10.312328Z",
     "shell.execute_reply.started": "2025-02-02T13:46:03.496588Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "\n",
    "```shell\n",
    "$ tree -L 1 cifar-10                                    \n",
    "cifar-10\n",
    "├── sampleSubmission.csv\n",
    "├── test\n",
    "├── train\n",
    "└── trainLabels.csv\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T08:46:20.671352Z",
     "iopub.status.busy": "2025-02-02T08:46:20.670696Z",
     "iopub.status.idle": "2025-02-02T08:46:21.897520Z",
     "shell.execute_reply": "2025-02-02T08:46:21.896297Z",
     "shell.execute_reply.started": "2025-02-02T08:46:20.671318Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "kaggle                            1.6.17\n",
      "\u001b[33mWARNING: Certificate did not match expected hostname: mirrors.cloud.aliyuncs.com. Certificate: {'subject': ((('countryName', 'CN'),), (('stateOrProvinceName', 'ZheJiang'),), (('localityName', 'HangZhou'),), (('organizationName', 'Alibaba (China) Technology Co., Ltd.'),), (('commonName', 'mirrors-ssl.aliyuncs.com'),)), 'issuer': ((('countryName', 'BE'),), (('organizationName', 'GlobalSign nv-sa'),), (('commonName', 'GlobalSign GCC R3 OV TLS CA 2024'),)), 'version': 3, 'serialNumber': '26C9CD393ADD2BCC646C9EC0', 'notBefore': 'Nov 21 03:52:05 2024 GMT', 'notAfter': 'Sep  4 00:00:00 2025 GMT', 'subjectAltName': (('DNS', 'mirrors-ssl.aliyuncs.com'),), 'OCSP': ('http://ocsp.globalsign.com/gsgccr3ovtlsca2024',), 'caIssuers': ('http://secure.globalsign.com/cacert/gsgccr3ovtlsca2024.crt',), 'crlDistributionPoints': ('http://crl.globalsign.com/gsgccr3ovtlsca2024.crl',)}\u001b[0m\u001b[33m\n",
      "\u001b[0m"
     ]
    }
   ],
   "source": [
    "!pip list|grep kaggle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T08:46:25.213557Z",
     "iopub.status.busy": "2025-02-02T08:46:25.213009Z",
     "iopub.status.idle": "2025-02-02T08:46:26.012913Z",
     "shell.execute_reply": "2025-02-02T08:46:26.011781Z",
     "shell.execute_reply.started": "2025-02-02T08:46:25.213513Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "- path is now set to: /content\n"
     ]
    }
   ],
   "source": [
    "!mkdir -p ~/.kaggle\n",
    "!cp /mnt/workspace/kaggle.json ~/.kaggle/\n",
    "!chmod 600 ~/.kaggle/kaggle.json\n",
    "!kaggle config set -n path -v /content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T08:54:23.151708Z",
     "iopub.status.busy": "2025-02-02T08:54:23.151146Z",
     "iopub.status.idle": "2025-02-02T08:55:13.950081Z",
     "shell.execute_reply": "2025-02-02T08:55:13.948604Z",
     "shell.execute_reply.started": "2025-02-02T08:54:23.151667Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading cifar-10.zip to /content/competitions/cifar-10\n",
      "100%|███████████████████████████████████████▉| 714M/715M [00:48<00:00, 18.4MB/s]\n",
      "100%|████████████████████████████████████████| 715M/715M [00:48<00:00, 15.5MB/s]\n"
     ]
    }
   ],
   "source": [
    "!kaggle competitions download -c cifar-10 #下载比赛数据集"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:00:56.194458Z",
     "iopub.status.busy": "2025-02-02T09:00:56.193939Z",
     "iopub.status.idle": "2025-02-02T09:00:56.321322Z",
     "shell.execute_reply": "2025-02-02T09:00:56.320227Z",
     "shell.execute_reply.started": "2025-02-02T09:00:56.194407Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cifar-10.zip\n"
     ]
    }
   ],
   "source": [
    "!ls /content/competitions/cifar-10/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T08:59:47.673242Z",
     "iopub.status.busy": "2025-02-02T08:59:47.672635Z",
     "iopub.status.idle": "2025-02-02T08:59:52.610038Z",
     "shell.execute_reply": "2025-02-02T08:59:52.608898Z",
     "shell.execute_reply.started": "2025-02-02T08:59:47.673200Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Archive:  /content/competitions/cifar-10/cifar-10.zip\n",
      "  inflating: sampleSubmission.csv    \n",
      "  inflating: test.7z                 \n",
      "  inflating: train.7z                \n",
      "  inflating: trainLabels.csv         \n"
     ]
    }
   ],
   "source": [
    "!unzip /content/competitions/cifar-10/cifar-10.zip"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:01:19.213703Z",
     "iopub.status.busy": "2025-02-02T09:01:19.213181Z",
     "iopub.status.idle": "2025-02-02T09:01:19.340760Z",
     "shell.execute_reply": "2025-02-02T09:01:19.339628Z",
     "shell.execute_reply.started": "2025-02-02T09:01:19.213660Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "01-classification-cnn.ipynb\t     new_resnet-aliyun.ipynb\n",
      "04_10_monkeys_model_1_aliyun.ipynb   new-vgg-aliyun.ipynb\n",
      "05_10_monkeys_resnet50_aliyun.ipynb  runs\n",
      "06_cifar10_model_2-aliyun.ipynb      sampleSubmission.csv\n",
      "checkpoints\t\t\t     test.7z\n",
      "data\t\t\t\t     train.7z\n",
      "kaggle.json\t\t\t     trainLabels.csv\n",
      "new_inception_net2-aliyun.ipynb\n"
     ]
    }
   ],
   "source": [
    "!ls"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:01:38.868317Z",
     "iopub.status.busy": "2025-02-02T09:01:38.867808Z",
     "iopub.status.idle": "2025-02-02T09:02:10.559089Z",
     "shell.execute_reply": "2025-02-02T09:02:10.558152Z",
     "shell.execute_reply.started": "2025-02-02T09:01:38.868278Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://mirrors.cloud.aliyuncs.com/pypi/simple\n",
      "Collecting py7zr\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/d0/59/dd1750002c0f46099281116f8165247bc62dc85edad41cdd26e7b26de19d/py7zr-0.22.0-py3-none-any.whl (67 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m67.9/67.9 kB\u001b[0m \u001b[31m17.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting texttable (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/24/99/4772b8e00a136f3e01236de33b0efda31ee7077203ba5967fcc76da94d65/texttable-1.7.0-py2.py3-none-any.whl (10 kB)\n",
      "Collecting pycryptodomex>=3.16.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/46/3f/f5bef92b11750af9e3516d4e69736eeeff20a2818d34611508bef5a7b381/pycryptodomex-3.21.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.3 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.3/2.3 MB\u001b[0m \u001b[31m63.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hCollecting pyzstd>=0.15.9 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/6b/3e/e4c7f449af9d19975ff5d333a58330317cf8b05fe4754106c694a29e7c25/pyzstd-0.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (413 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m413.7/413.7 kB\u001b[0m \u001b[31m69.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting pyppmd<1.2.0,>=1.1.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/8f/f7/be56704aef53490da6bbe6e2e7efff3c13383310ac71f8a82d15d84bbedb/pyppmd-1.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (138 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m139.0/139.0 kB\u001b[0m \u001b[31m33.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting pybcj<1.1.0,>=1.0.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/d0/6a/3937d0915590d91ccab40b687a1c87429b17325240e8db7e9ad828c9cae9/pybcj-1.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (49 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m49.6/49.6 kB\u001b[0m \u001b[31m13.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting multivolumefile>=0.2.3 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/22/31/ec5f46fd4c83185b806aa9c736e228cb780f13990a9cf4da0beb70025fcc/multivolumefile-0.2.3-py3-none-any.whl (17 kB)\n",
      "Collecting inflate64<1.1.0,>=1.0.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/b3/3f/1af3164e8bda3fdce692c260f01e37c0af21c3022e7227795524505d8b21/inflate64-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (93 kB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m93.3/93.3 kB\u001b[0m \u001b[31m25.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25hCollecting brotli>=1.1.0 (from py7zr)\n",
      "  Downloading https://mirrors.cloud.aliyuncs.com/pypi/packages/d5/00/40f760cc27007912b327fe15bf6bfd8eaecbe451687f72a8abc587d503b3/Brotli-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (3.0 MB)\n",
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m3.0/3.0 MB\u001b[0m \u001b[31m67.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25hRequirement already satisfied: psutil in /usr/local/lib/python3.10/site-packages (from py7zr) (6.1.1)\n",
      "Installing collected packages: texttable, brotli, pyzstd, pyppmd, pycryptodomex, pybcj, multivolumefile, inflate64, py7zr\n",
      "Successfully installed brotli-1.1.0 inflate64-1.0.1 multivolumefile-0.2.3 py7zr-0.22.0 pybcj-1.0.3 pycryptodomex-3.21.0 pyppmd-1.1.1 pyzstd-0.16.2 texttable-1.7.0\n",
      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
      "\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.0\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip install py7zr\n",
    "import py7zr\n",
    "a =py7zr.SevenZipFile(r'./train.7z','r')\n",
    "a.extractall(path=r'./competitions/cifar-10/')\n",
    "a.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:02:10.560823Z",
     "iopub.status.busy": "2025-02-02T09:02:10.560445Z",
     "iopub.status.idle": "2025-02-02T09:04:28.490079Z",
     "shell.execute_reply": "2025-02-02T09:04:28.489233Z",
     "shell.execute_reply.started": "2025-02-02T09:02:10.560790Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "a =py7zr.SevenZipFile(r'./test.7z','r')\n",
    "a.extractall(path=r'./competitions/cifar-10/')\n",
    "a.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:04:28.491685Z",
     "iopub.status.busy": "2025-02-02T09:04:28.491355Z",
     "iopub.status.idle": "2025-02-02T09:04:28.744976Z",
     "shell.execute_reply": "2025-02-02T09:04:28.743813Z",
     "shell.execute_reply.started": "2025-02-02T09:04:28.491659Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "50000\n"
     ]
    }
   ],
   "source": [
    "!ls competitions/cifar-10/train/ |wc -l  #wc -l统计数量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:51:56.103713Z",
     "start_time": "2025-01-20T07:51:54.990355Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:46:19.550819Z",
     "iopub.status.busy": "2025-02-02T13:46:19.550173Z",
     "iopub.status.idle": "2025-02-02T13:46:22.352311Z",
     "shell.execute_reply": "2025-02-02T13:46:22.351543Z",
     "shell.execute_reply.started": "2025-02-02T13:46:19.550781Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR = Path(\".\")\n",
    "DATA_DIR1 =Path(\"competitions/cifar-10/\")\n",
    "train_lables_file = DATA_DIR / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR / \"sampleSubmission.csv\" #测试集模板csv文件\n",
    "train_folder = DATA_DIR1 / \"train\"\n",
    "test_folder = DATA_DIR1 / \"test\"\n",
    "\n",
    "#所有的类别\n",
    "class_names = [\n",
    "    'airplane',\n",
    "    'automobile',\n",
    "    'bird',\n",
    "    'cat',\n",
    "    'deer',\n",
    "    'dog',\n",
    "    'frog',\n",
    "    'horse',\n",
    "    'ship',\n",
    "    'truck',\n",
    "]\n",
    "\n",
    "def parse_csv_file(filepath, folder): #filepath:csv文件路径，folder:图片所在文件夹\n",
    "    \"\"\"Parses csv files into (filename(path), label) format\"\"\"\n",
    "    results = []\n",
    "    #读取所有行\n",
    "    with open(filepath, 'r') as f:\n",
    "#         lines = f.readlines()  为什么加[1:]，可以试这个\n",
    "        #第一行不需要，因为第一行是标题\n",
    "        lines = f.readlines()[1:] \n",
    "    for line in lines:#依次去取每一行\n",
    "        image_id, label_str = line.strip('\\n').split(',') #图片id 和标签分离\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        results.append((image_full_path, label_str)) #得到对应图片的路径和分类\n",
    "    return results\n",
    "\n",
    "#解析对应的文件夹\n",
    "train_labels_info = parse_csv_file(train_lables_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "#打印\n",
    "import pprint\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T07:52:54.792839Z",
     "start_time": "2025-01-20T07:52:54.757327Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:46:23.649654Z",
     "iopub.status.busy": "2025-02-02T13:46:23.649154Z",
     "iopub.status.idle": "2025-02-02T13:46:23.751294Z",
     "shell.execute_reply": "2025-02-02T13:46:23.750528Z",
     "shell.execute_reply.started": "2025-02-02T13:46:23.649620Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# train_df = pd.DataFrame(train_labels_info)\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000]) # 取前45000张图片作为训练集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:]) # 取后5000张图片作为验证集\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:08:23.507372Z",
     "start_time": "2025-01-20T08:08:22.474521Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:46:29.282194Z",
     "iopub.status.busy": "2025-02-02T13:46:29.281332Z",
     "iopub.status.idle": "2025-02-02T13:46:32.193662Z",
     "shell.execute_reply": "2025-02-02T13:46:32.192842Z",
     "shell.execute_reply.started": "2025-02-02T13:46:29.282152Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    label_to_idx = {label: idx for idx, label in enumerate(class_names)} # 类别映射为idx\n",
    "    idx_to_label = {idx: label for idx, label in enumerate(class_names)} # idx映射为类别,为了test测试集使用\n",
    "    def __init__(self, mode, transform=None):\n",
    "        self.df = self.df_map.get(mode, None) # 获取对应模式的df，不同字符串对应不同模式\n",
    "        if self.df is None:\n",
    "            raise ValueError(\"mode should be one of train, val, test, but got {}\".format(mode))\n",
    "        # assert self.df, \"df is None\"\n",
    "        self.transform = transform\n",
    "        \n",
    "    def __getitem__(self, index):\n",
    "        img_path, label = self.df.iloc[index] # 获取图片路径和标签\n",
    "        img = Image.open(img_path).convert('RGB')\n",
    "        # # img 转换为 channel first\n",
    "        # img = img.transpose((2, 0, 1))\n",
    "        # transform\n",
    "        img = self.transform(img) # 数据增强\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "    \n",
    "    def __len__(self):\n",
    "        return self.df.shape[0] # 返回df的行数,样本数\n",
    "    \n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "transforms_train = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)), #缩放\n",
    "        # random rotation 40\n",
    "        transforms.RandomRotation(40), #随机旋转\n",
    "        # horizaontal flip\n",
    "        transforms.RandomHorizontalFlip(),  #随机水平翻转\n",
    "        transforms.ToTensor(), #转换为tensor\n",
    "        transforms.Normalize(mean, std) #标准化\n",
    "    ]) #数据增强\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "        # resize\n",
    "        transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "        transforms.ToTensor(),\n",
    "        transforms.Normalize(mean, std)\n",
    "    ])\n",
    "# ToTensor还将图像的维度从[height, width, channels]转换为[channels, height, width]。\n",
    "train_ds = Cifar10Dataset(\"train\", transforms_train)\n",
    "eval_ds = Cifar10Dataset(\"eval\", transforms_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:04:58.140572Z",
     "iopub.status.busy": "2025-02-02T09:04:58.140077Z",
     "iopub.status.idle": "2025-02-02T09:04:58.152470Z",
     "shell.execute_reply": "2025-02-02T09:04:58.151779Z",
     "shell.execute_reply.started": "2025-02-02T09:04:58.140543Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "6"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:04:59.395349Z",
     "iopub.status.busy": "2025-02-02T09:04:59.394853Z",
     "iopub.status.idle": "2025-02-02T09:04:59.403210Z",
     "shell.execute_reply": "2025-02-02T09:04:59.402462Z",
     "shell.execute_reply.started": "2025-02-02T09:04:59.395317Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "228.68236"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sample1=train_ds[0][0]\n",
    "np.sum(sample1[2].numpy())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-24T12:26:52.532323800Z",
     "start_time": "2024-07-24T12:26:52.525328700Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-02-02T09:05:02.225530Z",
     "iopub.status.busy": "2025-02-02T09:05:02.225029Z",
     "iopub.status.idle": "2025-02-02T09:05:02.231449Z",
     "shell.execute_reply": "2025-02-02T09:05:02.230738Z",
     "shell.execute_reply.started": "2025-02-02T09:05:02.225498Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{0: 'airplane', 1: 'automobile', 2: 'bird', 3: 'cat', 4: 'deer', 5: 'dog', 6: 'frog', 7: 'horse', 8: 'ship', 9: 'truck'}\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'airplane': 0,\n",
       " 'automobile': 1,\n",
       " 'bird': 2,\n",
       " 'cat': 3,\n",
       " 'deer': 4,\n",
       " 'dog': 5,\n",
       " 'frog': 6,\n",
       " 'horse': 7,\n",
       " 'ship': 8,\n",
       " 'truck': 9}"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(train_ds.idx_to_label)  # 类别映射为idx\n",
    "train_ds.label_to_idx # idx映射为类别"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:08:29.255263Z",
     "start_time": "2025-01-20T08:08:29.252817Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:46:40.681617Z",
     "iopub.status.busy": "2025-02-02T13:46:40.680906Z",
     "iopub.status.idle": "2025-02-02T13:46:40.686496Z",
     "shell.execute_reply": "2025-02-02T13:46:40.685495Z",
     "shell.execute_reply.started": "2025-02-02T13:46:40.681567Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)   \n",
    "eval_dl = DataLoader(eval_ds, batch_size=batch_size, shuffle=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:05:18.335431Z",
     "iopub.status.busy": "2025-02-02T09:05:18.334871Z",
     "iopub.status.idle": "2025-02-02T09:05:18.474719Z",
     "shell.execute_reply": "2025-02-02T09:05:18.473577Z",
     "shell.execute_reply.started": "2025-02-02T09:05:18.335387Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-rw-r--r-- 1 root root 1953 10月 19  2013 /mnt/workspace/ch6/competitions/cifar-10/train/17742.png\n"
     ]
    }
   ],
   "source": [
    "!ls -l /mnt/workspace/ch6/competitions/cifar-10/train/17742.png"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:06:39.900222Z",
     "iopub.status.busy": "2025-02-02T09:06:39.899646Z",
     "iopub.status.idle": "2025-02-02T09:07:09.453327Z",
     "shell.execute_reply": "2025-02-02T09:07:09.452564Z",
     "shell.execute_reply.started": "2025-02-02T09:06:39.900181Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.4369, 0.4268, 0.3947]), tensor([0.2464, 0.2418, 0.2358]))\n"
     ]
    }
   ],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:49:56.579457400Z",
     "start_time": "2024-07-23T06:49:56.499468400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:46:56.003802Z",
     "iopub.status.busy": "2025-02-02T13:46:56.003303Z",
     "iopub.status.idle": "2025-02-02T13:46:56.138863Z",
     "shell.execute_reply": "2025-02-02T13:46:56.137878Z",
     "shell.execute_reply.started": "2025-02-02T13:46:56.003764Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "             model.0.weight             paramerters num: 3456\n",
      "              model.0.bias              paramerters num: 128\n",
      "             model.2.weight             paramerters num: 128\n",
      "              model.2.bias              paramerters num: 128\n",
      "             model.3.weight             paramerters num: 147456\n",
      "              model.3.bias              paramerters num: 128\n",
      "             model.5.weight             paramerters num: 128\n",
      "              model.5.bias              paramerters num: 128\n",
      "             model.7.weight             paramerters num: 294912\n",
      "              model.7.bias              paramerters num: 256\n",
      "             model.9.weight             paramerters num: 256\n",
      "              model.9.bias              paramerters num: 256\n",
      "            model.10.weight             paramerters num: 589824\n",
      "             model.10.bias              paramerters num: 256\n",
      "            model.12.weight             paramerters num: 256\n",
      "             model.12.bias              paramerters num: 256\n",
      "            model.14.weight             paramerters num: 1179648\n",
      "             model.14.bias              paramerters num: 512\n",
      "            model.16.weight             paramerters num: 512\n",
      "             model.16.bias              paramerters num: 512\n",
      "            model.17.weight             paramerters num: 2359296\n",
      "             model.17.bias              paramerters num: 512\n",
      "            model.19.weight             paramerters num: 512\n",
      "             model.19.bias              paramerters num: 512\n",
      "            model.22.weight             paramerters num: 4194304\n",
      "             model.22.bias              paramerters num: 512\n",
      "            model.24.weight             paramerters num: 5120\n",
      "             model.24.bias              paramerters num: 10\n"
     ]
    }
   ],
   "source": [
    "class CNN(nn.Module):\n",
    "    def __init__(self, num_classes):\n",
    "        super().__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=3, out_channels=128, kernel_size=3, padding=\"same\"),\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(128),# 批标准化，在通道数做的归一化\n",
    "            nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=\"same\"), #输出尺寸（128，32，32）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(128),\n",
    "            nn.MaxPool2d(kernel_size=2), #输出尺寸（128，16，16）\n",
    "            nn.Conv2d(in_channels=128, out_channels=256, kernel_size=3, padding=\"same\"),\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(256),\n",
    "            nn.Conv2d(in_channels=256, out_channels=256, kernel_size=3, padding=\"same\"), #输出尺寸（256，16，16）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(256),\n",
    "            nn.MaxPool2d(kernel_size=2),#输出尺寸（256，8，8）\n",
    "            nn.Conv2d(in_channels=256, out_channels=512, kernel_size=3, padding=\"same\"),\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(512),\n",
    "            nn.Conv2d(in_channels=512, out_channels=512, kernel_size=3, padding=\"same\"), #输出尺寸（512，8，8）\n",
    "            nn.ReLU(),\n",
    "            nn.BatchNorm2d(512),\n",
    "            nn.MaxPool2d(kernel_size=2), #输出尺寸（512，4，4）\n",
    "            nn.Flatten(), #展平\n",
    "            nn.Linear(8192, 512),\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(512, num_classes),\n",
    "        ) #Sequential自动连接各层，把各层的输出作为下一层的输入\n",
    "        \n",
    "    def forward(self, x):\n",
    "        return self.model(x)\n",
    "        \n",
    "for key, value in CNN(len(class_names)).named_parameters():\n",
    "    print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:49:59.043655400Z",
     "start_time": "2024-07-23T06:49:58.976307900Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-02-02T09:07:09.551548Z",
     "iopub.status.busy": "2025-02-02T09:07:09.551066Z",
     "iopub.status.idle": "2025-02-02T09:07:09.618565Z",
     "shell.execute_reply": "2025-02-02T09:07:09.617914Z",
     "shell.execute_reply.started": "2025-02-02T09:07:09.551522Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total trainable parameters: 8779914\n"
     ]
    }
   ],
   "source": [
    "total_params = sum(p.numel() for p in CNN(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total trainable parameters: {total_params}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:49:14.250276700Z",
     "start_time": "2024-07-23T06:49:14.242278700Z"
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-02-02T09:07:09.620451Z",
     "iopub.status.busy": "2025-02-02T09:07:09.620106Z",
     "iopub.status.idle": "2025-02-02T09:07:09.625391Z",
     "shell.execute_reply": "2025-02-02T09:07:09.624726Z",
     "shell.execute_reply.started": "2025-02-02T09:07:09.620426Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "8192"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "512*4*4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:18:30.878296Z",
     "start_time": "2025-01-20T08:18:30.872291Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:07:09.626478Z",
     "iopub.status.busy": "2025-02-02T09:07:09.626159Z",
     "iopub.status.idle": "2025-02-02T09:07:09.635658Z",
     "shell.execute_reply": "2025-02-02T09:07:09.635021Z",
     "shell.execute_reply.started": "2025-02-02T09:07:09.626455Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([32, 3, 64, 64])"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "input_4d = torch.randn(32, 3, 64, 64)  # 32 个样本，3 个通道，图像大小为 64x64\n",
    "bn2d = nn.BatchNorm2d(3)              # 对 3 个通道进行归一化\n",
    "output_4d = bn2d(input_4d)\n",
    "output_4d.shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:18:32.896417Z",
     "start_time": "2025-01-20T08:18:32.888443Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:07:09.636726Z",
     "iopub.status.busy": "2025-02-02T09:07:09.636410Z",
     "iopub.status.idle": "2025-02-02T09:07:09.652245Z",
     "shell.execute_reply": "2025-02-02T09:07:09.651525Z",
     "shell.execute_reply.started": "2025-02-02T09:07:09.636703Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[[ 3.9962e-01, -2.4952e-01, -2.9845e-02,  ..., -1.3868e+00,\n",
       "           -2.2958e+00, -1.1064e+00],\n",
       "          [-8.3918e-01,  5.9200e-01, -6.1287e-01,  ..., -3.7235e-01,\n",
       "           -2.6176e-01, -1.0483e-01],\n",
       "          [ 6.1583e-01,  5.7285e-02, -3.0743e-01,  ..., -9.8284e-01,\n",
       "            1.9219e+00, -4.3373e-01],\n",
       "          ...,\n",
       "          [ 4.5745e-01, -5.4372e-01,  1.6008e+00,  ...,  2.2015e+00,\n",
       "            1.0615e+00, -6.3294e-01],\n",
       "          [ 3.2250e+00, -1.7320e+00,  2.1355e+00,  ...,  1.4462e-01,\n",
       "           -5.2783e-01, -8.0691e-01],\n",
       "          [-9.6785e-01,  1.0964e+00, -1.0009e-02,  ...,  1.2441e+00,\n",
       "           -9.3838e-01, -1.4306e+00]],\n",
       "\n",
       "         [[-3.1594e-01,  8.8906e-01,  1.2927e+00,  ...,  1.8564e+00,\n",
       "            1.1166e+00,  3.5003e-01],\n",
       "          [-2.5437e-01, -4.9439e-01, -8.3911e-01,  ..., -6.3087e-01,\n",
       "            6.2507e-01,  3.2198e-02],\n",
       "          [ 2.3960e-01,  1.0927e-01, -3.5107e-01,  ..., -1.0512e+00,\n",
       "           -3.5346e-01, -6.1399e-01],\n",
       "          ...,\n",
       "          [-2.2206e-01,  7.3251e-02, -1.3287e+00,  ..., -1.1475e-01,\n",
       "            1.3068e+00, -3.0916e-01],\n",
       "          [ 9.3296e-01,  1.7000e-01,  1.4967e+00,  ...,  1.1164e-02,\n",
       "           -1.4787e+00, -7.2746e-01],\n",
       "          [ 4.9271e-01, -3.1447e-01, -2.1925e+00,  ...,  1.9255e+00,\n",
       "            5.0626e-01,  9.6767e-02]],\n",
       "\n",
       "         [[-7.1782e-01,  4.0245e-01,  6.4781e-01,  ...,  9.1027e-02,\n",
       "            6.7020e-02, -1.6383e+00],\n",
       "          [ 1.0846e+00, -4.6971e-03,  5.5980e-01,  ..., -1.1183e+00,\n",
       "           -4.8021e-01,  1.3474e+00],\n",
       "          [ 3.2823e-01,  1.7394e+00,  4.1042e-01,  ..., -4.8471e-01,\n",
       "           -9.9093e-01, -1.3430e+00],\n",
       "          ...,\n",
       "          [ 7.0846e-01, -4.8184e-01,  1.6405e+00,  ...,  2.6777e-02,\n",
       "           -5.4388e-01,  6.1697e-01],\n",
       "          [ 1.4045e+00,  5.8610e-01, -1.5625e-02,  ...,  2.1015e-01,\n",
       "            1.4870e+00, -3.0291e-01],\n",
       "          [-4.3683e-02,  5.1907e-01, -5.3569e-01,  ..., -8.8031e-01,\n",
       "           -1.7958e-02,  4.5098e-01]]],\n",
       "\n",
       "\n",
       "        [[[ 8.8077e-01,  2.8234e-01, -1.3136e+00,  ...,  5.9547e-01,\n",
       "           -8.4697e-01, -1.1437e+00],\n",
       "          [-1.7519e-01,  1.2084e+00,  8.2713e-01,  ...,  1.0751e+00,\n",
       "            1.7098e-01, -4.8170e-01],\n",
       "          [ 1.4450e+00,  1.9068e+00, -1.1981e-01,  ..., -1.9605e-01,\n",
       "           -1.0172e+00,  6.5610e-01],\n",
       "          ...,\n",
       "          [-1.0600e+00,  1.2634e+00,  8.5481e-01,  ...,  2.8242e-01,\n",
       "           -7.8910e-01, -1.3249e+00],\n",
       "          [-9.0096e-01,  1.3794e+00, -1.5692e-01,  ..., -3.6781e-01,\n",
       "           -1.2199e-01,  1.6306e-01],\n",
       "          [-2.8369e-01, -1.5135e+00, -4.5240e-01,  ..., -1.4974e+00,\n",
       "            1.9670e-01,  1.5739e+00]],\n",
       "\n",
       "         [[-3.6750e-01, -1.1664e+00,  4.9602e-01,  ..., -1.1356e+00,\n",
       "           -2.4701e+00, -1.0249e+00],\n",
       "          [ 1.0625e+00, -2.3990e+00,  1.1784e+00,  ...,  4.4970e-02,\n",
       "            3.0618e+00, -1.6749e-01],\n",
       "          [ 5.7455e-01, -2.5422e-01,  1.3142e+00,  ...,  2.0745e+00,\n",
       "           -4.1121e-02,  4.5805e-01],\n",
       "          ...,\n",
       "          [-9.3956e-01,  6.2738e-01,  4.0872e-02,  ...,  8.9222e-01,\n",
       "           -1.8859e-01,  1.3037e+00],\n",
       "          [ 5.9016e-01, -2.5306e+00, -8.2224e-01,  ..., -4.1542e-02,\n",
       "            3.5659e-01, -8.5053e-01],\n",
       "          [ 9.1591e-01, -6.1478e-01, -3.1792e-01,  ...,  6.7609e-01,\n",
       "           -7.0363e-01, -1.7859e-01]],\n",
       "\n",
       "         [[ 4.5038e-01,  1.7506e-01, -1.2347e+00,  ..., -2.4721e-01,\n",
       "           -8.3519e-01, -7.1577e-01],\n",
       "          [-1.1434e+00, -7.6182e-01,  3.0370e-01,  ...,  9.4070e-01,\n",
       "            9.8569e-01, -7.2013e-01],\n",
       "          [-1.9618e-01, -1.8847e-01, -1.1572e+00,  ..., -2.6554e-01,\n",
       "            1.2261e+00, -1.0388e+00],\n",
       "          ...,\n",
       "          [-2.1911e-01, -1.5834e+00,  5.3471e-01,  ...,  4.9358e-02,\n",
       "            2.4574e-01, -2.3771e-01],\n",
       "          [ 2.1023e-01,  1.1662e+00,  1.7033e+00,  ..., -1.9027e+00,\n",
       "           -1.0972e-02,  2.4503e-01],\n",
       "          [ 1.4507e+00, -5.7636e-01,  1.7515e+00,  ...,  4.3464e-01,\n",
       "           -1.7798e-01,  1.4895e+00]]],\n",
       "\n",
       "\n",
       "        [[[ 2.0054e-01,  9.2512e-01, -7.3045e-01,  ..., -1.0429e+00,\n",
       "            1.8694e+00, -6.2868e-02],\n",
       "          [ 1.5549e-01,  4.8900e-01,  7.9233e-01,  ...,  1.8972e+00,\n",
       "            6.2964e-01,  1.9918e+00],\n",
       "          [-4.8086e-01, -1.4680e+00, -4.5226e-01,  ...,  1.9465e-02,\n",
       "           -3.6939e-01, -5.8578e-01],\n",
       "          ...,\n",
       "          [-9.9214e-02, -1.2785e+00, -2.2621e-01,  ...,  1.2745e-01,\n",
       "            9.2782e-06,  1.4692e+00],\n",
       "          [ 6.3226e-01, -1.6711e+00,  3.0052e-01,  ..., -1.6252e+00,\n",
       "           -7.8273e-01, -2.0535e+00],\n",
       "          [-1.3529e+00, -3.0640e-01, -1.1829e+00,  ...,  1.1697e+00,\n",
       "            1.0740e+00,  8.7546e-01]],\n",
       "\n",
       "         [[-3.1818e-01, -3.3310e-01,  6.9587e-01,  ...,  6.2204e-01,\n",
       "            3.2523e-01,  7.2171e-01],\n",
       "          [-1.5050e+00, -5.4920e-01,  1.1277e+00,  ..., -1.0829e+00,\n",
       "            3.4534e-01, -1.0100e+00],\n",
       "          [ 2.6709e-01, -2.1098e+00,  1.9304e+00,  ..., -1.7725e-01,\n",
       "           -2.0792e-01, -1.5390e+00],\n",
       "          ...,\n",
       "          [ 2.2755e+00,  1.0567e+00, -1.4494e+00,  ..., -4.7512e-01,\n",
       "           -2.3552e-01,  4.8110e-02],\n",
       "          [-1.5364e-01, -3.1697e-01, -1.9232e-01,  ...,  8.3995e-01,\n",
       "            3.4064e-01,  1.1864e+00],\n",
       "          [ 1.4600e+00,  8.7749e-01, -1.1649e+00,  ..., -3.2082e-01,\n",
       "            2.1953e+00, -7.9319e-02]],\n",
       "\n",
       "         [[-5.8550e-01,  8.8045e-01, -1.1598e+00,  ..., -2.9818e-02,\n",
       "           -3.2684e-01, -3.1301e-01],\n",
       "          [-3.5846e-02,  3.3133e-01, -2.4221e-01,  ...,  4.5308e-01,\n",
       "            3.3645e-01,  1.3096e+00],\n",
       "          [ 4.3220e-01, -1.0998e-01, -7.6924e-01,  ..., -1.0755e+00,\n",
       "            2.9845e-01,  1.8277e+00],\n",
       "          ...,\n",
       "          [-7.9735e-01,  6.8057e-01,  1.8319e+00,  ...,  5.5339e-01,\n",
       "            6.2701e-01, -7.0654e-01],\n",
       "          [ 4.7918e-01, -7.9127e-01, -1.2393e+00,  ...,  9.2153e-01,\n",
       "            9.9985e-01,  4.6160e-01],\n",
       "          [ 4.4191e-01, -6.3409e-01, -1.1254e-01,  ...,  1.0647e+00,\n",
       "            2.0996e-01,  6.3082e-01]]],\n",
       "\n",
       "\n",
       "        ...,\n",
       "\n",
       "\n",
       "        [[[ 5.2519e-01, -2.3297e-02,  2.1103e+00,  ..., -1.6779e+00,\n",
       "            3.4388e-01, -2.8607e-01],\n",
       "          [ 9.7107e-01, -1.6284e+00,  1.1454e-01,  ...,  4.5372e-01,\n",
       "            1.1991e+00, -1.1370e+00],\n",
       "          [-9.4323e-01, -1.3238e+00, -1.2370e+00,  ...,  9.2311e-02,\n",
       "            4.6581e-01,  3.2537e+00],\n",
       "          ...,\n",
       "          [-2.3287e-01,  1.6026e-01, -1.3110e-01,  ..., -3.9720e-01,\n",
       "           -6.2529e-01,  1.1623e-01],\n",
       "          [-4.8488e-01, -5.5454e-01,  3.8717e-01,  ..., -2.4100e-01,\n",
       "           -5.2419e-01,  2.0811e-01],\n",
       "          [-9.5355e-01,  2.7899e-01,  9.5467e-01,  ..., -1.1843e+00,\n",
       "           -1.4089e-01, -3.7253e-02]],\n",
       "\n",
       "         [[ 1.2850e-01,  1.2619e+00, -1.2362e-01,  ...,  1.4929e+00,\n",
       "            5.3757e-01,  4.7327e-02],\n",
       "          [-3.2977e-01, -3.3133e-01,  1.9220e+00,  ...,  3.2643e-01,\n",
       "           -9.4428e-01,  7.0455e-01],\n",
       "          [ 7.2081e-01, -1.1144e+00,  3.6651e-03,  ...,  5.6515e-01,\n",
       "           -1.0258e+00,  1.5025e+00],\n",
       "          ...,\n",
       "          [ 1.8492e+00, -4.6354e-01, -1.4349e+00,  ...,  1.2190e+00,\n",
       "           -8.3854e-01,  8.8342e-01],\n",
       "          [ 9.1643e-01, -3.7784e-01,  9.8735e-01,  ..., -1.0086e+00,\n",
       "           -6.4855e-01,  4.3748e-02],\n",
       "          [-5.4073e-01,  1.0365e+00,  1.4946e+00,  ...,  4.2488e-01,\n",
       "            1.9448e+00, -1.6318e-01]],\n",
       "\n",
       "         [[-2.0972e-01,  5.6673e-01, -9.0471e-01,  ...,  3.2614e-01,\n",
       "            5.7510e-01,  7.5119e-01],\n",
       "          [ 6.8157e-02, -1.3174e+00, -1.0908e-01,  ..., -1.1113e+00,\n",
       "           -1.9694e+00,  3.0883e+00],\n",
       "          [ 1.1834e+00,  4.4229e-01, -3.1025e-01,  ...,  2.4415e-01,\n",
       "           -2.5011e+00, -2.0150e+00],\n",
       "          ...,\n",
       "          [-6.0336e-01,  2.0083e+00,  3.2557e-01,  ..., -5.3693e-01,\n",
       "            4.7759e-02,  4.9702e-01],\n",
       "          [ 1.3849e-01, -3.0136e-01,  4.0039e-01,  ...,  6.4300e-01,\n",
       "           -5.4546e-01,  6.9634e-01],\n",
       "          [-1.4114e+00, -2.6051e+00, -1.5272e-01,  ...,  1.8503e-01,\n",
       "           -4.1860e-01, -1.6101e-01]]],\n",
       "\n",
       "\n",
       "        [[[-2.2556e+00,  1.3169e+00,  5.8763e-01,  ..., -4.1412e-01,\n",
       "           -5.7856e-01,  1.8190e-01],\n",
       "          [-8.1586e-01,  5.6887e-01, -3.7975e-01,  ...,  1.6076e+00,\n",
       "           -1.6575e+00,  8.1281e-02],\n",
       "          [ 1.4875e-01,  1.5085e+00, -4.4556e-02,  ...,  7.2910e-01,\n",
       "            1.0045e+00, -1.0516e+00],\n",
       "          ...,\n",
       "          [ 7.6370e-01, -1.5969e-01,  8.8344e-01,  ...,  4.1720e-01,\n",
       "            2.4321e-01, -7.8104e-01],\n",
       "          [-4.8212e-01,  1.3975e+00,  7.3802e-01,  ...,  1.7309e+00,\n",
       "            8.3433e-01, -1.1151e+00],\n",
       "          [-9.6927e-01,  4.6085e-01,  7.5828e-01,  ...,  4.4227e-02,\n",
       "            2.0124e+00, -1.5208e+00]],\n",
       "\n",
       "         [[ 6.2232e-01, -9.2700e-01, -4.0720e-01,  ...,  9.1430e-01,\n",
       "           -8.6543e-01,  6.0333e-01],\n",
       "          [-1.8122e-01,  1.1464e+00,  8.8223e-01,  ...,  9.8323e-01,\n",
       "           -2.9707e-01, -7.7224e-01],\n",
       "          [ 1.2423e+00,  4.7645e-01, -3.9545e-01,  ...,  1.5318e-01,\n",
       "            9.1104e-01, -3.4132e-01],\n",
       "          ...,\n",
       "          [ 1.2713e+00,  7.2463e-01, -1.7057e+00,  ..., -1.1345e+00,\n",
       "            9.3841e-01,  2.6560e+00],\n",
       "          [-2.7658e-01, -5.5461e-01,  1.4532e+00,  ...,  6.4134e-01,\n",
       "           -7.8236e-02, -4.5690e-01],\n",
       "          [-5.0173e-01,  8.4957e-01, -2.1894e-01,  ..., -5.9525e-01,\n",
       "           -1.0579e+00, -1.9043e+00]],\n",
       "\n",
       "         [[-8.1754e-01,  3.5274e-01,  2.1458e+00,  ...,  1.2145e+00,\n",
       "            7.3351e-01,  2.3191e-01],\n",
       "          [-6.7813e-01, -1.8534e+00, -8.6538e-01,  ...,  3.8259e-01,\n",
       "            6.3151e-01,  1.1060e+00],\n",
       "          [-1.0196e+00,  4.3566e-01, -6.6248e-01,  ...,  1.6179e+00,\n",
       "            3.1916e-01,  7.0894e-01],\n",
       "          ...,\n",
       "          [ 6.2388e-01,  4.1624e-01,  3.2835e-01,  ..., -1.1833e+00,\n",
       "           -1.5152e-01, -8.9186e-02],\n",
       "          [ 7.5428e-01, -5.9223e-02, -6.0459e-01,  ...,  7.5585e-01,\n",
       "           -1.8795e+00,  7.9339e-01],\n",
       "          [-3.8846e-01, -1.2686e-01,  7.5782e-02,  ..., -5.4239e-01,\n",
       "           -1.0058e+00,  1.8059e-01]]],\n",
       "\n",
       "\n",
       "        [[[-7.5866e-01, -1.2297e+00, -1.9689e+00,  ..., -2.9767e-01,\n",
       "           -1.3077e+00, -1.2942e+00],\n",
       "          [-1.3154e+00, -4.4251e-01,  9.9825e-01,  ...,  6.1086e-01,\n",
       "            4.3446e-01,  2.1567e+00],\n",
       "          [ 1.2819e+00,  1.3443e+00,  9.3598e-01,  ..., -7.4496e-01,\n",
       "            1.5857e+00,  1.9382e-01],\n",
       "          ...,\n",
       "          [-1.2518e+00, -1.2643e+00,  8.7250e-01,  ..., -1.8076e+00,\n",
       "            5.8550e-01,  8.4539e-01],\n",
       "          [-1.1853e+00,  1.3780e+00,  8.7594e-01,  ...,  3.5876e-01,\n",
       "            1.2350e+00, -1.0836e-01],\n",
       "          [-2.0956e+00,  1.1140e+00,  4.5454e-01,  ...,  3.5674e-01,\n",
       "            1.2413e+00,  1.1879e+00]],\n",
       "\n",
       "         [[-5.4721e-01,  5.0649e-01,  1.1092e-01,  ...,  8.9196e-01,\n",
       "            2.2329e-01,  4.6424e-01],\n",
       "          [-4.0212e-01, -1.6536e+00, -5.3560e-01,  ...,  4.6926e-01,\n",
       "            4.0649e-01,  4.1536e-01],\n",
       "          [ 1.1186e+00,  1.2988e-01,  7.7271e-01,  ...,  5.3295e-01,\n",
       "            1.7909e+00, -2.2984e-01],\n",
       "          ...,\n",
       "          [ 1.0337e+00,  7.7787e-01,  6.2795e-01,  ..., -1.2655e-01,\n",
       "            2.1262e-01,  1.1514e+00],\n",
       "          [ 4.9830e-01, -1.1055e-01,  7.2625e-01,  ..., -9.3404e-02,\n",
       "            1.5029e-01, -1.1548e-01],\n",
       "          [ 3.8431e-01, -9.4475e-01,  1.8712e-01,  ..., -4.4577e-03,\n",
       "            1.7590e+00, -1.2004e-01]],\n",
       "\n",
       "         [[-8.3136e-01,  1.3979e+00, -5.6010e-01,  ...,  2.9430e-01,\n",
       "            6.8541e-01,  9.2043e-01],\n",
       "          [ 1.2133e+00, -9.4333e-01, -2.0949e+00,  ...,  7.5925e-01,\n",
       "            5.7873e-01, -1.4507e+00],\n",
       "          [ 6.4431e-01,  2.1126e+00, -2.3354e-01,  ...,  4.6127e-01,\n",
       "            4.0529e-01,  1.5371e+00],\n",
       "          ...,\n",
       "          [ 1.1787e-01, -2.8750e-01, -2.0561e-01,  ..., -1.2813e+00,\n",
       "            1.9407e+00, -2.0658e-02],\n",
       "          [-6.2737e-01,  5.9091e-01, -4.0413e-01,  ..., -8.4157e-01,\n",
       "           -6.4752e-01, -7.8481e-01],\n",
       "          [-2.6864e-01, -8.5330e-01, -1.7587e-01,  ..., -2.2964e-01,\n",
       "            6.6898e-01,  5.7479e-01]]]], grad_fn=<NativeBatchNormBackward0>)"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "output_4d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-20T08:18:50.605471Z",
     "start_time": "2025-01-20T08:18:50.598876Z"
    },
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:07:09.653323Z",
     "iopub.status.busy": "2025-02-02T09:07:09.652999Z",
     "iopub.status.idle": "2025-02-02T09:07:09.662954Z",
     "shell.execute_reply": "2025-02-02T09:07:09.662258Z",
     "shell.execute_reply.started": "2025-02-02T09:07:09.653299Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[[ 3.9962e-01, -2.4952e-01, -2.9845e-02,  ..., -1.3868e+00,\n",
       "           -2.2958e+00, -1.1064e+00],\n",
       "          [-8.3918e-01,  5.9200e-01, -6.1287e-01,  ..., -3.7235e-01,\n",
       "           -2.6176e-01, -1.0483e-01],\n",
       "          [ 6.1583e-01,  5.7285e-02, -3.0743e-01,  ..., -9.8284e-01,\n",
       "            1.9219e+00, -4.3373e-01],\n",
       "          ...,\n",
       "          [ 4.5745e-01, -5.4372e-01,  1.6008e+00,  ...,  2.2015e+00,\n",
       "            1.0615e+00, -6.3294e-01],\n",
       "          [ 3.2250e+00, -1.7320e+00,  2.1355e+00,  ...,  1.4462e-01,\n",
       "           -5.2783e-01, -8.0691e-01],\n",
       "          [-9.6785e-01,  1.0964e+00, -1.0009e-02,  ...,  1.2441e+00,\n",
       "           -9.3838e-01, -1.4306e+00]]],\n",
       "\n",
       "\n",
       "        [[[ 8.8077e-01,  2.8234e-01, -1.3136e+00,  ...,  5.9547e-01,\n",
       "           -8.4697e-01, -1.1437e+00],\n",
       "          [-1.7519e-01,  1.2084e+00,  8.2713e-01,  ...,  1.0751e+00,\n",
       "            1.7098e-01, -4.8170e-01],\n",
       "          [ 1.4450e+00,  1.9068e+00, -1.1981e-01,  ..., -1.9605e-01,\n",
       "           -1.0172e+00,  6.5610e-01],\n",
       "          ...,\n",
       "          [-1.0600e+00,  1.2634e+00,  8.5481e-01,  ...,  2.8242e-01,\n",
       "           -7.8910e-01, -1.3249e+00],\n",
       "          [-9.0096e-01,  1.3794e+00, -1.5692e-01,  ..., -3.6781e-01,\n",
       "           -1.2199e-01,  1.6306e-01],\n",
       "          [-2.8369e-01, -1.5135e+00, -4.5240e-01,  ..., -1.4974e+00,\n",
       "            1.9670e-01,  1.5739e+00]]],\n",
       "\n",
       "\n",
       "        [[[ 2.0054e-01,  9.2512e-01, -7.3045e-01,  ..., -1.0429e+00,\n",
       "            1.8694e+00, -6.2868e-02],\n",
       "          [ 1.5549e-01,  4.8900e-01,  7.9233e-01,  ...,  1.8972e+00,\n",
       "            6.2964e-01,  1.9918e+00],\n",
       "          [-4.8086e-01, -1.4680e+00, -4.5226e-01,  ...,  1.9465e-02,\n",
       "           -3.6939e-01, -5.8578e-01],\n",
       "          ...,\n",
       "          [-9.9214e-02, -1.2785e+00, -2.2621e-01,  ...,  1.2745e-01,\n",
       "            9.2781e-06,  1.4692e+00],\n",
       "          [ 6.3226e-01, -1.6711e+00,  3.0052e-01,  ..., -1.6252e+00,\n",
       "           -7.8273e-01, -2.0535e+00],\n",
       "          [-1.3529e+00, -3.0640e-01, -1.1829e+00,  ...,  1.1697e+00,\n",
       "            1.0740e+00,  8.7546e-01]]],\n",
       "\n",
       "\n",
       "        ...,\n",
       "\n",
       "\n",
       "        [[[ 5.2519e-01, -2.3297e-02,  2.1103e+00,  ..., -1.6779e+00,\n",
       "            3.4388e-01, -2.8607e-01],\n",
       "          [ 9.7107e-01, -1.6284e+00,  1.1454e-01,  ...,  4.5372e-01,\n",
       "            1.1991e+00, -1.1370e+00],\n",
       "          [-9.4323e-01, -1.3238e+00, -1.2370e+00,  ...,  9.2311e-02,\n",
       "            4.6581e-01,  3.2537e+00],\n",
       "          ...,\n",
       "          [-2.3287e-01,  1.6026e-01, -1.3110e-01,  ..., -3.9720e-01,\n",
       "           -6.2529e-01,  1.1623e-01],\n",
       "          [-4.8488e-01, -5.5454e-01,  3.8717e-01,  ..., -2.4100e-01,\n",
       "           -5.2419e-01,  2.0811e-01],\n",
       "          [-9.5355e-01,  2.7899e-01,  9.5467e-01,  ..., -1.1843e+00,\n",
       "           -1.4089e-01, -3.7253e-02]]],\n",
       "\n",
       "\n",
       "        [[[-2.2556e+00,  1.3169e+00,  5.8763e-01,  ..., -4.1412e-01,\n",
       "           -5.7856e-01,  1.8190e-01],\n",
       "          [-8.1586e-01,  5.6887e-01, -3.7975e-01,  ...,  1.6076e+00,\n",
       "           -1.6575e+00,  8.1281e-02],\n",
       "          [ 1.4875e-01,  1.5085e+00, -4.4556e-02,  ...,  7.2910e-01,\n",
       "            1.0045e+00, -1.0516e+00],\n",
       "          ...,\n",
       "          [ 7.6370e-01, -1.5969e-01,  8.8344e-01,  ...,  4.1720e-01,\n",
       "            2.4321e-01, -7.8104e-01],\n",
       "          [-4.8212e-01,  1.3975e+00,  7.3802e-01,  ...,  1.7309e+00,\n",
       "            8.3433e-01, -1.1151e+00],\n",
       "          [-9.6927e-01,  4.6085e-01,  7.5828e-01,  ...,  4.4227e-02,\n",
       "            2.0124e+00, -1.5208e+00]]],\n",
       "\n",
       "\n",
       "        [[[-7.5866e-01, -1.2297e+00, -1.9689e+00,  ..., -2.9767e-01,\n",
       "           -1.3077e+00, -1.2942e+00],\n",
       "          [-1.3154e+00, -4.4251e-01,  9.9825e-01,  ...,  6.1086e-01,\n",
       "            4.3446e-01,  2.1567e+00],\n",
       "          [ 1.2819e+00,  1.3443e+00,  9.3598e-01,  ..., -7.4496e-01,\n",
       "            1.5857e+00,  1.9382e-01],\n",
       "          ...,\n",
       "          [-1.2518e+00, -1.2643e+00,  8.7250e-01,  ..., -1.8076e+00,\n",
       "            5.8550e-01,  8.4539e-01],\n",
       "          [-1.1853e+00,  1.3780e+00,  8.7594e-01,  ...,  3.5876e-01,\n",
       "            1.2350e+00, -1.0836e-01],\n",
       "          [-2.0956e+00,  1.1140e+00,  4.5454e-01,  ...,  3.5674e-01,\n",
       "            1.2413e+00,  1.1879e+00]]]], grad_fn=<NativeBatchNormBackward0>)"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bn2d = nn.BatchNorm2d(1)              # 对 1个通道进行归一化\n",
    "output_4d1 = bn2d(input_4d[:, 0:1, :, :])\n",
    "output_4d1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:51:47.038660500Z",
     "start_time": "2024-07-23T06:51:46.193237300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:48:32.344643Z",
     "iopub.status.busy": "2025-02-02T13:48:32.344138Z",
     "iopub.status.idle": "2025-02-02T13:48:32.457775Z",
     "shell.execute_reply": "2025-02-02T13:48:32.456977Z",
     "shell.execute_reply.started": "2025-02-02T13:48:32.344596Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "        \n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "tags": []
   },
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:51:50.921239100Z",
     "start_time": "2024-07-23T06:51:49.522951600Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:48:33.130354Z",
     "iopub.status.busy": "2025-02-02T13:48:33.129363Z",
     "iopub.status.idle": "2025-02-02T13:48:33.358839Z",
     "shell.execute_reply": "2025-02-02T13:48:33.358076Z",
     "shell.execute_reply.started": "2025-02-02T13:48:33.130312Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "        \n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\", \n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "        \n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "        \n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "            \n",
    "        )\n",
    "    \n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "tags": []
   },
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:51:52.029341200Z",
     "start_time": "2024-07-23T06:51:52.023343500Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:48:33.814423Z",
     "iopub.status.busy": "2025-02-02T13:48:33.813761Z",
     "iopub.status.idle": "2025-02-02T13:48:33.821651Z",
     "shell.execute_reply": "2025-02-02T13:48:33.820726Z",
     "shell.execute_reply.started": "2025-02-02T13:48:33.814385Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch. \n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "        \n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "        \n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "        \n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true,
    "tags": []
   },
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-23T06:51:56.837899700Z",
     "start_time": "2024-07-23T06:51:56.828651400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:48:34.626427Z",
     "iopub.status.busy": "2025-02-02T13:48:34.625922Z",
     "iopub.status.idle": "2025-02-02T13:48:34.632569Z",
     "shell.execute_reply": "2025-02-02T13:48:34.631670Z",
     "shell.execute_reply.started": "2025-02-02T13:48:34.626392Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T13:48:35.160352Z",
     "iopub.status.busy": "2025-02-02T13:48:35.159504Z",
     "iopub.status.idle": "2025-02-02T13:48:35.525130Z",
     "shell.execute_reply": "2025-02-02T13:48:35.524275Z",
     "shell.execute_reply.started": "2025-02-02T13:48:35.160312Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1) #最大值的索引\n",
    "            \n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())     # 计算准确率\n",
    "                loss = loss.cpu().item() # 计算损失\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step # 记录每一步的损失和准确率\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "                    \n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step, \n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "                \n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 20\n",
    "\n",
    "model = CNN(num_classes=10)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 adam\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "# tensorboard_callback = TensorBoardCallback(\"runs/cifar-10\")\n",
    "# tensorboard_callback.draw_model(model, [1, 3, IMAGE_SIZE, IMAGE_SIZE])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(\"checkpoints/cifar-10\", save_step=len(train_dl), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "collapsed": false,
    "execution": {
     "iopub.execute_input": "2025-02-02T13:48:39.614020Z",
     "iopub.status.busy": "2025-02-02T13:48:39.613520Z",
     "iopub.status.idle": "2025-02-02T14:02:45.987361Z",
     "shell.execute_reply": "2025-02-02T14:02:45.986623Z",
     "shell.execute_reply.started": "2025-02-02T13:48:39.613981Z"
    },
    "jupyter": {
     "outputs_hidden": false
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 90%|█████████ | 12672/14080 [14:06<01:34, 14.97it/s, epoch=17] "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 18 / global_step 12672\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "record = training(\n",
    "    model,\n",
    "    train_dl,\n",
    "    eval_dl,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_dl)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T14:04:16.664026Z",
     "iopub.status.busy": "2025-02-02T14:04:16.663509Z",
     "iopub.status.idle": "2025-02-02T14:04:16.951359Z",
     "shell.execute_reply": "2025-02-02T14:04:16.950562Z",
     "shell.execute_reply.started": "2025-02-02T14:04:16.663987Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "image/png": "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Sy6hV69eNG3alD179lR9BSt069bNOb/AtU6dO3d2ziswGAyce+65vPDCC/z999/s2bOH33//HbDfQA4dOpQnn3ySNWvWkJSUxA8//FBt9Rc1U7jvj8zMzCp9fyxevJghQ4Zw11130adPHzp27MjOnTud+9PT02nbtq1X2nCHU045hf3797Nt27Yqq6MQolK8rB0sgY4QolZp27Yty5cvZ8+ePeTn5/v9NrlTp058//33rF27lnXr1nHttddWSc+MP/fddx+///47L774Itu2bePjjz/mjTfe4P777wfgl19+4bXXXmPt2rXs3buXTz75BJvNRpcuXVi+fDnPPfccK1euJCcnh++//54jR47QrVu3aqu/qJnCfX/cdtttVfr+6NSpEytXrmTOnDls27aNxx57jBUrVriVeeKJJ3jppZd47bXX2L59O6tXr+b1118H4KyzzuLMM8/ksssuIzs7m927d/Prr78ye/bsKquzqJ325Bdz8ZuLmbsxN+xzPP7TBibMWI2qqmw/XMRFby5mwdbQh0A7fPFXDpe/vYTjGjKhXfzmYvbk+8/Q+dfuY3R6bC4PLNdTVGbxWy5702G3er80dys3ffQXFmv1fY5CLQh0JP+AEMLV/fffj16vp3v37jRp0sTvnIKpU6fSoEEDhgwZwtixYxk1ahR9+/attnr27duXL7/8ku+//55TTjmFyZMn89RTTzFu3DgA6tevz/fff8/w4cPp1q0b06ZN44svvqBHjx5kZGSwcOFCxowZQ+fOnXn00Ud56aWXGD16dLXVX9RM4bw/LrroIoYPH16l74877riDSy+9lKuuuopBgwZx9OhR7rrrLrcyN954I6+88gpvvfUWPXr04IILLmD79u3O/d999x0DBgzgmmuuoXv37jz44IOaeq+ECMV936xj7b4T3P7pqrDP8fHSvfzy9yF25J1k/IzVrNt3gnEfrQh+oB+Tvl/Pyr3HeXXe9qBl1+47wf3frPO7/8p3lgJgsil8tGRPwHO51vv133cwf+sR5m89or3iUVCzB9ULIUSIOnfuzNKlS922OYIHV23btnUOA3MYP36823PPoTq+UhyfOHFCU718pUi+7LLLGDFiBBkZGV7zOE4//XSfKf3BPuxNvqkW4Qjn/WGz2SgsLOS+++5z+z2N5vsjOTmZjz76iI8++shtu2OBcoc77riDO+64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7LHHkJeXh59//rm5q+KxEydOYMqUKVixYgV8fHyauzoNoigK0tPT8dJLLwEAevfujby8PLz99tu4//77m7l27vviiy/wySef4NNPP0WPHj2wbds2TJ06FdHR0a2qHdcLk8mEe+65B0IIvPXWW81dHSIb9k0tA/smamre2C957YhOeHg4NBpNjUwqBQUFiIqKaqZaVZk8eTK+++47rFq1CrGxsbbjUVFRKC8vR2FhoUN5+3pHRUU5bZf1udrKGI1G+Pr6Nrj+mzdvxtmzZ5GamgqtVgutVouffvoJ//jHP6DVahEZGdkq2gEAbdu2Rffu3R2OdevWDcePH3eoS23vpaioKJw9e9bh+YqKCly8eNGj9jbE9OnTbX85S05Oxn333YcnnnjC9lfN1tKO6pqy3q7KqN0ua2dy7NgxrFixwvZXs9bYFvIM+yb2Te5i39Sy2lGdt/VN3toveW2go9frkZaWhpycHNsxRVGQk5ODjIyMZquXEAKTJ0/G119/jZUrV6J9+/YOz6elpUGn0znUe9++fTh+/Lit3hkZGdi5c6fDm876prR+KGZkZDicw1pGrbYPGTIEO3fuxLZt22z/0tPTMW7cONv3raEdADBgwIAaaVT379+PhIQEAED79u0RFRXlUI+ioiKsX7/eoS2FhYXYvHmzrczKlSuhKAr69etnK7N69WqYTCaHtnTp0gUhISENbkdpaSlk2fG/tEajgaIoraod1TVlvZvi/WbtTA4cOIAff/wRYWFhDs+3praQ59g3sW9yF/umltWO6rypb/LqfqnR0hy0AAsXLhQGg0F8+OGHYvfu3eLhhx8WwcHBDplUmtqjjz4qgoKCRG5urjhz5oztX2lpqa3MI488IuLj48XKlSvFpk2bREZGhsjIyLA9b019OXToULFt2zaxbNky0aZNG6epL6dPny727Nkj3nzzzUZL4Wlln9mmNbVjw4YNQqvVihdffFEcOHBAfPLJJ8LPz098/PHHtjLz5s0TwcHB4ptvvhE7duwQo0aNcppCsnfv3mL9+vXi559/FomJiQ6pFwsLC0VkZKS47777RF5enli4cKHw8/NTLYXn/fffL2JiYmwpPL/66isRHh4unnzyyRbfjuLiYrF161axdetWAUC89tprYuvWrbaML01V719++UVotVrx6quvij179ojZs2d7nPqytraUl5eL2267TcTGxopt27Y5fAbYZ6ppKW2hxsG+iX2TO9g3NX87vKVvup77Ja8OdIQQYsGCBSI+Pl7o9XrRt29fsW7dumatDwCn/z744ANbmatXr4o//OEPIiQkRPj5+Ynbb79dnDlzxuE8R48eFcOHDxe+vr4iPDxc/OlPfxImk8mhzKpVq0SvXr2EXq8XHTp0cLhGY6jembSmdnz77bciKSlJGAwG0bVrV/Hvf//b4XlFUcSzzz4rIiMjhcFgEEOGDBH79u1zKHPhwgUxduxYERAQIIxGo5gwYYIoLi52KLN9+3YxcOBAYTAYRExMjJg3b55qbSgqKhJTpkwR8fHxwsfHR3To0EE8/fTTDh9ULbUdq1atcvr/4v7772/yen/xxReic+fOQq/Xix49eojvv/9etbYcOXLE5WfAqlWrWlxbqPGwb2Lf5A72Tc3bDm/pm67nfkkSwm5rWiIiIiIiIi/gtWt0iIiIiIjo+sVAh4iIiIiIvA4DHSIiIiIi8joMdIiIiIiIyOsw0CEiIiIiIq/DQIeIiIiIiLwOAx0iIiIiIvI6DHSIiIiIiMjrMNAhUskDDzyA0aNHN3c1iIiIALBfImKgQ0REREREXoeBDpGHvvzySyQnJ8PX1xdhYWHIysrC9OnT8dFHH+Gbb76BJEmQJAm5ubkAgBMnTuCee+5BcHAwQkNDMWrUKBw9etR2Putf3J5//nm0adMGRqMRjzzyCMrLy5ungURE1KqwXyJyTtvcFSBqTc6cOYOxY8di/vz5uP3221FcXIw1a9Zg/PjxOH78OIqKivDBBx8AAEJDQ2EymZCdnY2MjAysWbMGWq0Wf/3rXzFs2DDs2LEDer0eAJCTkwMfHx/k5ubi6NGjmDBhAsLCwvDiiy82Z3OJiKiFY79E5BoDHSIPnDlzBhUVFbjjjjuQkJAAAEhOTgYA+Pr6oqysDFFRUbbyH3/8MRRFwXvvvQdJkgAAH3zwAYKDg5Gbm4uhQ4cCAPR6Pd5//334+fmhR48eeOGFFzB9+nTMmTMHssyBVyIico79EpFrfKcSeSAlJQVDhgxBcnIy7r77brz77ru4dOmSy/Lbt2/HwYMHERgYiICAAAQEBCA0NBTXrl3DoUOHHM7r5+dne5yRkYGSkhKcOHGiUdtDREStG/slItc4okPkAY1GgxUrVuDXX3/FDz/8gAULFuDpp5/G+vXrnZYvKSlBWloaPvnkkxrPtWnTprGrS0REXo79EpFrDHSIPCRJEgYMGIABAwZg1qxZSEhIwNdffw29Xg+z2exQNjU1FZ9//jkiIiJgNBpdnnP79u24evUqfH19AQDr1q1DQEAA4uLiGrUtRETU+rFfInKOU9eIPLB+/Xq89NJL2LRpE44fP46vvvoK586dQ7du3dCuXTvs2LED+/btw/nz52EymTBu3DiEh4dj1KhRWLNmDY4cOYLc3Fw8/vjjOHnypO285eXlmDhxInbv3o0lS5Zg9uzZmDx5MudBExFRrdgvEbnGER0iDxiNRqxevRpvvPEGioqKkJCQgL/97W8YPnw40tPTkZubi/T0dJSUlGDVqlW48cYbsXr1asyYMQN33HEHiouLERMTgyFDhjj8JW3IkCFITEzEoEGDUFZWhrFjx+K5555rvoYSEVGrwH6JyDVJCCGauxJE17MHHngAhYWFWLx4cXNXhYiIiP0SeQ2OPxIRERERkddhoENERERERF6HU9eIiIiIiMjrcESHiIiIiIi8DgMdIiIiIiLyOgx0iIiIiIjI6zDQISIiIiIir8NAh4iIiIiIvA4DHSIiIiIi8joMdIiIiIiIyOsw0CEiIiIiIq/DQIeIiIiIiLzO/wcKpGfYYOt55wAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):    \n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "    plt.show()\n",
    "\n",
    "    \n",
    "\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  # 横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T14:04:36.635243Z",
     "iopub.status.busy": "2025-02-02T14:04:36.634739Z",
     "iopub.status.idle": "2025-02-02T14:04:39.782442Z",
     "shell.execute_reply": "2025-02-02T14:04:39.781679Z",
     "shell.execute_reply.started": "2025-02-02T14:04:36.635210Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4283\n",
      "accuracy: 0.8736\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(\"checkpoints/cifar-10/best.ckpt\", weights_only=True, map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, eval_dl, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 推理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:23:04.989847Z",
     "iopub.status.busy": "2025-02-02T09:23:04.989333Z",
     "iopub.status.idle": "2025-02-02T09:25:48.899990Z",
     "shell.execute_reply": "2025-02-02T09:25:48.899227Z",
     "shell.execute_reply.started": "2025-02-02T09:23:04.989814Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 4688/4688 [02:43<00:00, 28.60it/s]\n"
     ]
    }
   ],
   "source": [
    "# test_df\n",
    "test_ds = Cifar10Dataset(\"test\", transform=transforms_eval)\n",
    "test_dl = DataLoader(test_ds, batch_size=batch_size, shuffle=False, drop_last=False)\n",
    "\n",
    "preds_collect = [] # 预测结果收集器\n",
    "model.eval()\n",
    "for data, fake_label in tqdm(test_dl):\n",
    "    data = data.to(device=device)\n",
    "    logits = model(data) #得到预测结果\n",
    "    preds = [test_ds.idx_to_label[idx] for idx in logits.argmax(axis=-1).cpu().tolist()]  # 得到预测类别，idx_to_label是id到字符串类别的映射\n",
    "    preds_collect.extend(preds)\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T09:31:05.887859Z",
     "iopub.status.busy": "2025-02-02T09:31:05.887321Z",
     "iopub.status.idle": "2025-02-02T09:31:05.898874Z",
     "shell.execute_reply": "2025-02-02T09:31:05.898097Z",
     "shell.execute_reply.started": "2025-02-02T09:31:05.887824Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>filepath</th>\n",
       "      <th>class</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>competitions/cifar-10/test/1.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>competitions/cifar-10/test/2.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>competitions/cifar-10/test/3.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>competitions/cifar-10/test/4.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>competitions/cifar-10/test/5.png</td>\n",
       "      <td>cat</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                           filepath class\n",
       "0  competitions/cifar-10/test/1.png   cat\n",
       "1  competitions/cifar-10/test/2.png   cat\n",
       "2  competitions/cifar-10/test/3.png   cat\n",
       "3  competitions/cifar-10/test/4.png   cat\n",
       "4  competitions/cifar-10/test/5.png   cat"
      ]
     },
     "execution_count": 67,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:31:06.608885Z",
     "iopub.status.busy": "2025-02-02T09:31:06.608358Z",
     "iopub.status.idle": "2025-02-02T09:31:06.697398Z",
     "shell.execute_reply": "2025-02-02T09:31:06.696570Z",
     "shell.execute_reply.started": "2025-02-02T09:31:06.608851Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299995</th>\n",
       "      <td>299996</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299996</th>\n",
       "      <td>299997</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299997</th>\n",
       "      <td>299998</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299998</th>\n",
       "      <td>299999</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>299999</th>\n",
       "      <td>300000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>300000 rows × 1 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "            id\n",
       "0            1\n",
       "1            2\n",
       "2            3\n",
       "3            4\n",
       "4            5\n",
       "...        ...\n",
       "299995  299996\n",
       "299996  299997\n",
       "299997  299998\n",
       "299998  299999\n",
       "299999  300000\n",
       "\n",
       "[300000 rows x 1 columns]"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_df1 = pd.DataFrame(list(range(1,3*10**5+1)), columns=[\"id\"])\n",
    "test_df1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T09:31:09.049945Z",
     "iopub.status.busy": "2025-02-02T09:31:09.049419Z",
     "iopub.status.idle": "2025-02-02T09:31:09.067192Z",
     "shell.execute_reply": "2025-02-02T09:31:09.066398Z",
     "shell.execute_reply.started": "2025-02-02T09:31:09.049910Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>id</th>\n",
       "      <th>label</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>1</td>\n",
       "      <td>deer</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>2</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>3</td>\n",
       "      <td>automobile</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>4</td>\n",
       "      <td>ship</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>5</td>\n",
       "      <td>airplane</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   id       label\n",
       "0   1        deer\n",
       "1   2    airplane\n",
       "2   3  automobile\n",
       "3   4        ship\n",
       "4   5    airplane"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_df1[\"label\"] = preds_collect # 增加预测类别列,比赛要求这一列是label\n",
    "test_df1.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-02-02T09:31:12.583624Z",
     "iopub.status.busy": "2025-02-02T09:31:12.583127Z",
     "iopub.status.idle": "2025-02-02T09:31:12.826384Z",
     "shell.execute_reply": "2025-02-02T09:31:12.825407Z",
     "shell.execute_reply.started": "2025-02-02T09:31:12.583592Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 导出 submission.csv\n",
    "test_df1.to_csv(\"submission.csv\", index=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-02-02T09:31:13.132620Z",
     "iopub.status.busy": "2025-02-02T09:31:13.132114Z",
     "iopub.status.idle": "2025-02-02T09:31:13.282693Z",
     "shell.execute_reply": "2025-02-02T09:31:13.281417Z",
     "shell.execute_reply.started": "2025-02-02T09:31:13.132585Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cifar-10  cnn-relu  monkeys-cnn-relu  monkeys-resnet50\n"
     ]
    }
   ],
   "source": [
    "!ls checkpoints/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
